간단한 블로그 형태 만들기
✒️ 2025-05-28 10:04 내용 수정
1. 데이터를 항목별로 분리했을 때
- 새 글을 작성하면 포스트 목록에 작성한 글이 뜨는 블로그를 만든다.
- state로 글을 작성하고 상태를 변화시켰기 때문에 새로고침을 하면 데이터가 사라진다.
/* eslint-disable */
import 'bootstrap/dist/css/bootstrap.min.css';
import './App.css';
import {useState} from 'react';
function App() {
let [title, setTitle] = useState(['부평 맛집', '오늘의 날씨', '봄 패션']);
let [date, setDate] = useState(['2024-01-15', '2024-02-27', '2024-03-05']);
let [likeHit, setLikeHit] = useState([0,0,0]);
let [content, setContent] = useState(['A동', '맑음', '코트']);
let [pick, setPick] = useState(0);
let [detail, setDetail] = useState(false);
let [inputTitle, setInputTitle] = useState('');
let [inputDate, setInputDate] = useState('');
let [inputContent, setInputContent] = useState('');
function LikeUp(i) { // 좋아요 버튼을 누르면 좋아요가 1씩 증가
let copy = [...likeHit];
copy[i]++;
setLikeHit(copy);
}
function postAdd() { // 새 글을 작성하면 각 state에 새 내용을 Array의 push()로 추가
if(inputTitle != '' && inputDate != '' && inputContent != ''){ // 값이 있을 때만 추가
// 기존 state를 깊은 복사로 복사하기
let copyTitle = [...title];
let copyLikeHit = [...likeHit];
let copyDate = [...date];
let copyContent = [...content];
// 복사한 배열에 새 state 내용을 push로 추가하기
// 가장 앞에 추가하고 싶다면 unshift()
copyTitle.push(inputTitle);
copyLikeHit.push(0);
copyDate.push(inputDate);
copyContent.push(inputContent);
// setState 함수로 복사한 배열 내용으로 state 업데이트하기
setTitle(copyTitle);
setLikeHit(copyLikeHit);
setDate(copyDate);
setContent(copyContent);
// input 데이터들과 input value 리셋
setInputTitle('');
setInputDate('');
setInputContent('');
document.getElementsByClassName("input-title")[0].value = '';
document.getElementsByClassName("input-date")[0].value = '';
document.getElementsByClassName("input-content")[0].value = '';
}
}
return (
<>
<section className='section sec'>
<div className='container-lg'>
<h2 className='title'>My Post</h2>
<div className='row row-cols-2 justify-content-center'>
<div className='col new-post p-4 m-3'>
// 입력을 받으면 input 내의 있는 값을 setState 함수들로 새로 업데이트 해준다.
<label>제목</label>
<input type='text' className='form-control input-title' onChange={(e)=>{setInputTitle(e.target.value)}}></input>
<label>날짜</label>
<input type='date' className='form-control input-date' onChange={(e)=>{setInputDate(e.target.value)}}></input>
<label>내용</label>
<textarea className='form-control input-content' onChange={(e)=>{setInputContent(e.target.value)}}></textarea>
// 버튼을 누르면 업데이트 된 내용들을 새로 state의 데이터에 추가해준다.
<button className='btn btn-success' onClick={()=>{postAdd()}}>확인</button>
</div>
</div>
<div className='row row-cols-2'>
{
title.map((el, i) => {
return(
// 아래에 선언한 함수에서 태그 내용을 만들고, 여기선 state 데이터들을 넘겨준다.
<Box key={i} title={title} date={date}
likeHit={likeHit} LikeUp={LikeUp} i={i}
pick={pick} setPick={setPick}
detail={detail} setDetail={setDetail}></Box>
);
})
}
</div>
<div className='row'>
{
// 항목을 눌렀을 때만 렌더링 되도록 설정한다.
(detail) ? <Detail i={pick} title={title} date={date} content={content}></Detail> : null
}
</div>
</div>
</section>
</>
);
}
// 각 포스트 박스의 내용을 만드는 함수
function Box(props) {
// App에서 <Box>로부터 넘어온 데이터들과 함수를 가져온다.
let {i, title, date, likeHit, LikeUp, pick, setPick, detail, setDetail} = props;
// 항목을 눌렀을 때 이전에 선택한 pick과 동일한 숫자면 닫기를
// 다른 숫자라면 그대로 보여지도록 설정한다.
function detailOpen(i) {
if(pick == i) {
setDetail(!detail);
} else {
detail = true;
setDetail(detail);
}
setPick(i);
}
return(
<div className='col box py-3'>
<div className='gt'>
// 제목을 누르면 상세 항목 박스를 보이도록 설정한다.
<h3 className='post-title' onClick={()=>{detailOpen(i)}}>{title[i]}</h3>
// 이모티콘을 누르면 좋아요 숫자가 올라가도록 설정한다.
<h5 className='like-btn'><span className='emoji' onClick={()=>LikeUp(i)}>👍</span>{likeHit[i]}</h5>
<p>{date[i]}</p>
</div>
</div>
)
}
// 각 포스트의 상세 내용을 보는 박스를 만드는 함수
function Detail(props) {
// App에서 <Detail>로부터 넘어온 데이터들을 가져온다.
let {i, title, date, content} = props;
return(
<div className='detail'>
<div className='gt'>
// 박스 안에 상세 내용을 출력한다.
<h3>{title[i]}</h3>
<p>{date[i]}</p>
<p>{content[i]}</p>
</div>
</div>
)
}
export default App;
- css도 적용한다.
*{margin:0; padding:0; box-sizing: border-box;}
ul, ol, li{list-style: none;}
a{text-decoration: none;}
body{background-color: #d4d4d4;}
.header{
width: 100%;
padding: 30px 0;
background-color: #444;
}
.header a{color:#fff;}
.sec{
width: 100%;
padding:50px 0;
}
.sec .new-post{
background-color: #fff;
border-radius: 10px;
box-shadow: 0 0 10px #7e7e7e;
}
.sec .new-post label{
margin:10px 0;
}
.sec .new-post .btn{
margin-top:10px;
float: right;
}
.sec .gt{
padding: 30px;
background-color: #fff;
border-radius: 10px;
box-shadow: 0 0 10px #7e7e7e;
}
.sec .post-title{
-ms-user-select: none; /* 글을 누를 때마다 드래그가 되지 않도록 설정 */
-moz-user-select: none;
-khtml-user-select: none;
-webkit-user-select: none;
user-select: none;
cursor: pointer; /* 글에 마우스를 올려 놓으면 포인터 모양으로 바뀌게 설정 */
}
.sec .post-title:hover{
color:blue;
}
.like-btn{
-ms-user-select: none;
-moz-user-select: none;
-khtml-user-select: none;
-webkit-user-select: none;
user-select: none;
}
.like-btn .emoji{
padding-right: 10px;
cursor: pointer;
}
.detail{
padding: 30px;
}
- 메인 화면에선 미리 state로 추가한 데이터 3개만 뜬다.
- 제목을 클릭하면 상세보기 창이 뜨며, 좋아요 숫자는 제목마다 다르게 올라간다.
- 다른 포스트의 제목을 누르면 해당 포스트의 상세보기 창으로 데이터가 바뀐다.
- 새로운 제목, 날짜, 내용을 입력하고 입력 버튼을 누르면 새 포스트가 추가된다.
- 이 과정 모두 새로고침 없이 진행된다.
- 새로 추가한 포스트의 제목을 누르면 상세보기가 뜬다.
2. 데이터를 객체 형태로 작성했을 때
- 데이터를 객체 형식으로 만들면 서로 연관 있는 데이터를 관리하기 더 수월하다.
- 배열 정렬은 배열#Array 객체의 메소드과 컬쳐N라이프's 자바스크립트 javascript array sort 정렬 를 참고.
- 다만 정렬 버튼을 눌렀을 때 정렬되는 것이 아니라 새 포스트 추가 시 변경되는 걸 감지했을 때 정렬된다.
/* eslint-disable */
import 'bootstrap/dist/css/bootstrap.min.css';
import './App.css';
import {useState} from 'react';
function App() {
// 항목별로 각각 데이터를 만들었을 때
let [title, setTitle] = useState(['부평 맛집', '오늘의 날씨', '봄 패션']);
let [date, setDate] = useState(['2024-01-15', '2024-02-27', '2024-03-05']);
let [likeHit, setLikeHit] = useState([0,0,0]);
let [content, setContent] = useState(['A동', '맑음', '코트']);
let [pick, setPick] = useState(0);
let [detail, setDetail] = useState(false);
let [inputTitle, setInputTitle] = useState('');
let [inputDate, setInputDate] = useState('');
let [inputContent, setInputContent] = useState('');
// 객체 배열인 state 객체 만들기
let [post, setPost] = useState([
{
title: '오늘 배운 내용',
date: '2024-02-27',
likeHit: 0,
content: 'React로 블로그 만들기'
},
{
title: '오늘 날씨',
date: '2024-02-15',
likeHit: 2,
content: '매우 추움'
},
{
title: '부평 맛집',
date: '2024-03-05',
likeHit: 10,
content: '프랭크버거'
},
{
title: '봄 의상 추천',
date: '2024-04-01',
likeHit: 5,
content: '긴 상하의에 코트'
}
]);
let [inputTitle2, setInputTitle2] = useState('');
let [inputDate2, setInputDate2] = useState('');
let [inputContent2, setInputContent2] = useState('');
function LikeUp(i) {
let copy = [...likeHit];
copy[i]++;
setLikeHit(copy);
}
// 항목별 데이터 배열에 새 데이터를 추가할 때
function postAdd() {
if(inputTitle != '' && inputDate != '' && inputContent != ''){
let copyTitle = [...title];
let copyLikeHit = [...likeHit];
let copyDate = [...date];
let copyContent = [...content];
// 가장 앞에 추가하고 싶다면 unshift()
copyTitle.push(inputTitle);
copyLikeHit.push(0);
copyDate.push(inputDate);
copyContent.push(inputContent);
setTitle(copyTitle);
setLikeHit(copyLikeHit);
setDate(copyDate);
setContent(copyContent);
setInputTitle('');
setInputDate('');
setInputContent('');
document.getElementsByClassName("input-title")[0].value = '';
document.getElementsByClassName("input-date")[0].value = '';
document.getElementsByClassName("input-content")[0].value = '';
}
}
// 객체 배열에 데이터를 새로 추가할 때
// 위의 과정과 비교하면 훨씬 간단하다
function postJsonAdd() {
if(inputTitle2 != '' && inputDate2 != '' && inputContent2 != '') {
let copy = [...post];
let newPost = {
title: inputTitle2,
date: inputDate2,
likeHit: 0,
content: inputContent2
}
copy.push(newPost);
setPost(copy);
setInputTitle2('');
setInputDate2('');
setInputContent2('');
document.getElementsByClassName("input-title2")[0].value = '';
document.getElementsByClassName("input-date2")[0].value = '';
document.getElementsByClassName("input-content2")[0].value = '';
}
}
// 날짜 또는 추천수 기준 정렬
function sortPost(num) {
let copy = [...post];
let date_select = document.getElementsByClassName("date-sort")[0].value;
let likeHit_select = document.getElementsByClassName("likeHit-sort")[0].value;
let date_default = document.getElementsByClassName("default-option")[0];
let likeHit_default = document.getElementsByClassName("default-option")[1];
if(num == 1) { // 날짜 기준 정렬 시
if(date_select == '오름차순') { // 오름차순 선택
copy.sort((a, b) => { // (a.date - b.date) 로 작성해도 무관
if(a.date < b.date) return -1;
if(a.date > b.date) return 1;
return 0;
});
} else if (date_select == '내림차순') { // 내림차순 선택
copy.sort((a, b) => { // (b.date - a.date) 로 작성해도 무관
if(a.date < b.date) return 1;
if(a.date > b.date) return -1;
return 0;
});
}
} else if(num == 2) { // 추천 기준 정렬 시
if(likeHit_select == '오름차순') { // 오름차순 선택
copy.sort((a, b) => { // (a.date - b.date) 로 작성해도 무관
if(a.likeHit < b.likeHit) return -1;
if(a.likeHit > b.likeHit) return 1;
return 0;
});
} else if (likeHit_select == '내림차순') { // 내림차순 선택
copy.sort((a, b) => { // (b.date - a.date) 로 작성해도 무관
if(a.likeHit < b.likeHit) return 1;
if(a.likeHit > b.likeHit) return -1;
return 0;
});
}
}
setPost(copy); // 정렬한 배열을 setState 적용
}
return (
<>
<section className='section sec'>
<div className='container-lg'>
<h2 className='title'>My Post</h2>
<div className='row row-cols-2 justify-content-center'>
<div className='col new-post p-4 m-3'>
<label>제목</label>
<input type='text' className='form-control input-title' onChange={(e)=>{setInputTitle(e.target.value)}}></input>
<label>날짜</label>
<input type='date' className='form-control input-date' onChange={(e)=>{setInputDate(e.target.value)}}></input>
<label>내용</label>
<textarea className='form-control input-content' onChange={(e)=>{setInputContent(e.target.value)}}></textarea>
<button className='btn btn-success' onClick={()=>{postAdd()}}>확인</button>
</div>
</div>
<div className='row row-cols-2'>
{
title.map((el, i) => {
return(
<Box key={i} title={title} date={date}
likeHit={likeHit} LikeUp={LikeUp} i={i}
pick={pick} setPick={setPick}
detail={detail} setDetail={setDetail}></Box>
);
})
}
</div>
<div className='row'>
{
(detail) ? <Detail i={pick} title={title} date={date} content={content}></Detail> : null
}
</div>
</div>
</section>
<hr></hr>
// 비교를 위해 객체 배열로 만든 블로그는 아래에 따로 section을 작성함
<section className='sec'>
<div className='container-lg'>
<h2 className='title'>Memo Post</h2>
<div className='row row-cols-2 justify-content-center'>
<div className='col new-post p-4 m-3'>
<label>제목</label>
<input type='text' className='form-control input-title2' onChange={(e)=>{setInputTitle2(e.target.value)}}></input>
<label>날짜</label>
<input type='date' className='form-control input-date2' onChange={(e)=>{setInputDate2(e.target.value)}}></input>
<label>내용</label>
<textarea className='form-control input-content2' onChange={(e)=>{setInputContent2(e.target.value)}}></textarea>
<button className='btn btn-success' onClick={()=>{postJsonAdd()}}>확인</button>
</div>
</div>
<div className='btn-wrap'>
<label>날짜</label>
<select className='select date-sort' onChange={()=>{sortPost(1)}}>
<option className='default-option'>----</option>
<option>오름차순</option>
<option>내림차순</option>
</select>
<label>추천수</label>
<select className='select likeHit-sort' onChange={()=>{sortPost(2)}}>
<option className='default-option'>----</option>
<option>오름차순</option>
<option>내림차순</option>
</select>
</div>
<div className='row row-cols-2'>
{
post.map((el, i) => {
return(
<Post key={i} post={post} i={i} setPost={setPost}></Post>
);
})
}
</div>
</div>
</section>
</>
);
}
// 항목별로 데이터 배열을 만들었을 때
function Box(props) {
let {i, title, date, likeHit, LikeUp, pick, setPick, detail, setDetail} = props;
function detailOpen(i) {
if(pick == i) {
setDetail(!detail);
} else {
detail = true;
setDetail(detail);
}
setPick(i);
}
return(
<div className='col box py-3'>
<div className='gt'>
<h3 className='post-title' onClick={()=>{detailOpen(i)}}>{title[i]}</h3>
<h5 className='like-btn'><span className='emoji' onClick={()=>LikeUp(i)}>👍</span>{likeHit[i]}</h5>
<p>{date[i]}</p>
</div>
</div>
)
}
// Box에서 넘겨주는 데이터 수를 비교했을 때 훨씬 간단하다.
function Post(props) {
let {i, post, setPost} = props;
function likeUp(i) {
let copy = [...post];
copy[i].likeHit++;
setPost(copy);
}
return(
<div className='col box py-3'>
<div className='gt'>
<h3 className='post-title'>{post[i].title}</h3>
<h5 className='like-btn'><span className='emoji' onClick={()=>{likeUp(i)}}>👍</span>{post[i].likeHit}</h5>
<p>{post[i].date}</p>
<p>{post[i].content}</p>
</div>
</div>
)
}
function Detail(props) {
let {i, title, date, content} = props;
return(
<div className='detail'>
<div className='gt'>
<h3>{title[i]}</h3>
<p>{date[i]}</p>
<p>{content[i]}</p>
</div>
</div>
)
}
export default App;
- 객체로 데이터를 표시할 때 코드가 더 간결해졌다.
- 위에는 겹치는 내용이므로 아래 부분만 캡쳐했다.
- 날짜 오름차순을 선택하면 포스트들의 날짜를 오름차순으로 정렬할 수 있다. 내림차순도 마찬가지로 적용된다.
- 추천수를 내림차순으로 정렬하면 포스트를 내림차순으로 정렬할 수 있고, 오름차순으로 해도 적용된다.